Conversation
Values aren't actually saving though
Use full entry types isntead of IDs
There was a problem hiding this comment.
Pull request overview
This PR bridges Craft’s legacy CP JavaScript/CSS behavior into the new Inertia + Vue CP so that Inertia-rendered pages (notably Section settings) can still open/use legacy slideouts and nested UI.
Changes:
- Inject CP legacy asset output (
headHtml/bodyHtml) into the Inertia root view and add a composable for appending/deduping injected HTML. - Scope/reset legacy styling via
.cp-legacy*classes and adjust legacy UI components (slideouts/HUDs/menus) to work alongside new markup. - Add/adjust resources + Vue UI for Entry Type selection, plus tests around input namespacing and script tag generation.
Reviewed changes
Copilot reviewed 87 out of 111 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| yii2-adapter/package-lock.json | Adds optional dependency metadata (lockfile churn). |
| yii2-adapter/legacy/web/assets/tailwindreset/src/tailwind_reset.css | Scopes Tailwind base reset to legacy containers via @scope. |
| yii2-adapter/legacy/web/assets/tailwindreset/dist/css/tailwind_reset.css | Built output reflecting scoped reset. |
| yii2-adapter/legacy/web/assets/tailwindreset/dist/css/tailwind_reset.css.map | Source map update for scoped reset. |
| yii2-adapter/legacy/web/assets/pluginstore/dist/js/app.js.LICENSE.txt | Updates bundled license text (ApexCharts version/license header change). |
| yii2-adapter/legacy/web/assets/cp/src/js/SortableCheckboxSelect.js | Re-init handling + disclosure trigger lookup tweaks. |
| yii2-adapter/legacy/web/assets/cp/src/js/Slideout.js | Adds .cp-legacy classes to slideout containers. |
| yii2-adapter/legacy/web/assets/cp/src/js/FieldLayoutDesigner.js | Adds .cp-legacy HUD class; attempts to init SortableCheckboxSelect. |
| yii2-adapter/legacy/web/assets/cp/src/css/craft.scss | Reorganizes legacy CSS imports and wraps most legacy styles under .cp-legacy. |
| yii2-adapter/legacy/web/assets/cp/src/css/_slideouts.scss | New/isolated slideout styling. |
| yii2-adapter/legacy/web/assets/cp/src/css/_menu.scss | New/isolated menu styling. |
| yii2-adapter/legacy/web/assets/cp/src/css/_hud.scss | New/isolated HUD/modal styling. |
| yii2-adapter/.stylelintrc.json | Disables no-invalid-position-at-import-rule rule globally. |
| tests/Unit/View/InputNamespaceTest.php | Adds test for excluding name attributes on non-form elements. |
| tests/Unit/View/HtmlStackTest.php | Updates expectation for module script output. |
| tests/Unit/Support/HtmlTest.php | Adds fixtures ensuring excluded elements aren’t namespaced. |
| tests/Unit/Cp/IconsTest.php | Adds tests for icon family/path/data resolution helpers. |
| src/View/HtmlStack.php | Emits {% js %} output as <script type="module">…</script>. |
| src/Support/Html.php | Excludes slot/craft-icon tags from namespacing name= attributes. |
| src/Section/Resources/SectionResource.php | Serializes entry types via EntryTypeResource collection. |
| src/Providers/AppServiceProvider.php | Disables JsonResource wrapping globally; minor URL facade import adjustments. |
| src/Http/Middleware/HandleInertiaRequests.php | Registers CP assets + injects headHtml/bodyHtml into Inertia root view. |
| src/Http/Controllers/Settings/EntryTypesController.php | Adds resource response for override settings + casts ID input to int. |
| src/Entry/Resources/EntryTypeResource.php | New JSON resource for entry type data incl. actions/indicators/icon metadata. |
| src/Entry/Data/EntryType.php | Uses event delegation for slideout action binding (dynamic DOM compatibility). |
| src/Cp/Icons.php | Adds resolve helpers (family/path/data) and refactors svg loading to reuse them. |
| src/Cp/Html/ElementHtml.php | Switches chip rendering to custom elements (craft-chip, craft-icon) + slot-based layout. |
| resources/views/app.blade.php | Inertia root view now includes injected legacy asset HTML. |
| resources/templates/_layouts/base.twig | Adds legacy classes + legacy globals injection for Twig CP path. |
| resources/templates/_includes/forms/entry-type-select/selection-settings.twig | Updates container markup + uses delegated dblclick/taphold handler. |
| resources/templates/_includes/disclosuremenu.twig | Switches invoker to craft-button with icon/appearance props. |
| resources/js/types/index.ts | Updates EntryType/icon/action/indicator typing for new payload shapes. |
| resources/js/types/globals.d.ts | Consolidates legacy Craft typings into a global types file. |
| resources/js/pages/SettingsSectionsEditPage.vue | Adjusts entry type form model + prop names for new EntryTypeSelect API. |
| resources/js/layout/AppLayout.vue | Adds (currently-unused) useAppendHtml() call. |
| resources/js/Craft.d.ts | Removes old temporary Craft typings file. |
| resources/js/composables/useReorderableRows.ts | Migrates row reordering to new generic drag/drop composable. |
| resources/js/composables/useReorderableItems.ts | New generic reorderable-items composable. |
| resources/js/composables/useDragAndDrop.ts | New generic drag/drop implementation using Atlaskit pragmatic DnD. |
| resources/js/composables/useAppendHtml.ts | New wrapper composable around CP DOM append utilities. |
| resources/js/components/Tooltip/Tooltip.vue | New Vue tooltip wrapper using c-tooltip. |
| resources/js/components/ReorderButton.vue | Adds cursor: grab styling. |
| resources/js/components/Pane.vue | Renames pane BEM classes to cp-pane* variants. |
| resources/js/components/form/EntryTypeSelect.vue | Major refactor: chip rendering, drag-reorder, legacy slideout override settings, create button. |
| resources/js/components/EntryType/EntryTypeChip.vue | New chip UI for entry types (actions/indicators/drag handle). |
| resources/js/components/EntryType/CreateEntryTypeButton.vue | New “Create” button that opens legacy screen slideout and restores focus. |
| resources/js/components/DropIndicator.vue | Expands indicator behavior/props and styling. |
| resources/js/components/DragShadow.vue | New drag placeholder/shadow component. |
| resources/js/components/ActionMenu.vue | Tweaks action item binding (id/icon/variant/click handling). |
| resources/build/useFetch.js | Build artifact updates (hashed imports). |
| resources/build/useEditableTable.js | Build artifact updates (hashed imports). |
| resources/build/Updater.js | Build artifact updates (hashed imports). |
| resources/build/SettingsSitesIndex.js | Build artifact updates (pagination param + hashed imports). |
| resources/build/SettingsSectionsIndexPage.js | Build artifact updates (pagination param + hashed imports). |
| resources/build/SettingsIndexPage.js | Build artifact updates (hashed imports). |
| resources/build/Pane.js | Build artifact updates matching cp-pane* rename. |
| resources/build/nav-list.ts.js | Build artifact updates (hashed import). |
| resources/build/nav-list-Cg7ivPRt.js | New build artifact for nav list component. |
| resources/build/nav-item.ts.js | Build artifact updates (hashed import). |
| resources/build/nav-item-ChCEIDF_.js | Build artifact updates (hashed import). |
| resources/build/ModalForm.js | Build artifact updates (hashed imports). |
| resources/build/manifest.json | Manifest updated for new hashed chunk names and entries. |
| resources/build/legacy.js | Build artifact updates (hashed deps + runtime helper name change). |
| resources/build/IndexLayout.js | Build artifact updates (hashed imports). |
| resources/build/DeleteSiteModal.js | Build artifact updates (hashed imports). |
| resources/build/decorate-EBysIGtV.js | New build artifact chunk. |
| resources/build/CpQueueIndicator.js | Build artifact updates (hashed import). |
| resources/build/CpGlobalSidebar.js | Build artifact updates (hashed import). |
| resources/build/CalloutReadOnly.js | Build artifact updates (hashed import). |
| resources/build/assets/SettingsSectionsEditPage.css | Build artifact updates for new entry type chip/dnd styles. |
| resources/build/assets/Pane.css | Build artifact updates for cp-pane* styles. |
| resources/build/assets/AppLayout.css | Build artifact updates (scoped hash changes). |
| resources/build/assets/AdminTable.css | Build artifact updates for drop indicator + reorder button cursor. |
| resources/build/AppLayout.js | Build artifact updates (includes inlined append HTML helper). |
| packages/craftcms-cp/src/utilities/icons.ts | Adjusts custom icon resolution to consider family as well as variant. |
| packages/craftcms-cp/src/utilities/dom.ts | New DOM utilities for appending/deduping injected head/body HTML. |
| packages/craftcms-cp/src/utilities/dom.test.ts | New tests for DOM append/dedup behavior. |
| packages/craftcms-cp/src/styles/shared/colorable.css | Regenerates color classes to cp-color-* and adds semantic colors. |
| packages/craftcms-cp/src/styles/shared/base.css | Adds .cp-code and adjusts hr to logical properties. |
| packages/craftcms-cp/src/index.ts | Exports new DOM utilities. |
| packages/craftcms-cp/src/components/tooltip/tooltip.ts | Tooltip styling changes (WebAwesome tooltip). |
| packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts | Story switches to <c-tooltip>. |
| packages/craftcms-cp/src/components/chip/chip.ts | Chip markup/class changes + icon slot support. |
| packages/craftcms-cp/src/components/chip/chip.styles.ts | Chip styles updated for new cp-chip* classes. |
| packages/craftcms-cp/src/components/button/button.ts | Adds TODO comment placeholder for status slot/region. |
| packages/craftcms-cp/scripts/generate-colors.js | Updates generator to produce cp-color-* + semantic colors. |
| resources/js/composables/useTableDragAndDrop.ts | Removes old table-specific drag/drop composable. |
Files not reviewed (1)
- yii2-adapter/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #[Override] | ||
| public function handle(Request $request, Closure $next): Response | ||
| { | ||
| Craft::$app->view->registerAssetBundle(CpAsset::class); | ||
| $htmlStack = app(HtmlStack::class); | ||
|
|
||
| View::composer('app', function ($view) use ($htmlStack) { | ||
| $view->with([ | ||
| 'headHtml' => $htmlStack->headHtml(), | ||
| 'bodyHtml' => $htmlStack->bodyHtml(), | ||
| ]); | ||
| }); | ||
|
|
||
| return parent::handle($request, $next); | ||
| } |
There was a problem hiding this comment.
View::composer('app', ...) is being registered inside handle(), which runs on every request. In long-running PHP environments (Octane/Swoole/RoadRunner) this will accumulate composers over time (memory/perf leak) and can cause headHtml() / bodyHtml() to be computed multiple times per render. Register the composer once (e.g. in a service provider boot method), or replace with a per-request View::share()/$request->attributes approach without global registration.
| // Elements that use name= for non-form purposes | ||
| $excludedElements = ['slot', 'craft-icon']; | ||
|
|
||
| $html = preg_replace_callback( | ||
| '/<('.implode('|', $excludedElements).')\b[^>]*>/i', | ||
| function (array $match) use (&$markers): string { | ||
| $marker = sprintf('{ns-marker:%s}', mt_rand()); | ||
| $markers[$marker] = $match[0]; | ||
|
|
||
| return $marker; | ||
| }, | ||
| $html | ||
| ) ?? $html; |
There was a problem hiding this comment.
The marker generation uses mt_rand() and doesn’t guarantee uniqueness. A collision would overwrite a previous entry in $markers, causing incorrect strtr() restoration (HTML corruption). Use a collision-resistant marker (e.g. incrementing counter, Str::uuid(), or bin2hex(random_bytes())) and/or loop until the marker key is unused.
| onCheck: function () { | ||
| this.$actionMenuBtn = this.$item.find('[data-disclosure-trigger]'); | ||
|
|
||
| if (this.$actionMenuBtn) { | ||
| this.onUncheck(); | ||
| } |
There was a problem hiding this comment.
this.$actionMenuBtn is a jQuery object; if (this.$actionMenuBtn) will always be truthy even when it’s empty. As written, onCheck() will always call onUncheck(), which can remove/destroy state unnecessarily. Check .length (or similar) before treating the element as present.
| <template v-for="entryType in selectableTypes" :key="entryType.id"> | ||
| <craft-action-item | ||
| @click="handleTypeSelect(type)" | ||
| @click="handleTypeSelect(entryType)" | ||
| type="checkbox" | ||
| :icon="type.icon ?? 'empty'" | ||
| :checked="modelValue.includes(type.id)" | ||
| :data-color="type.color?.value ?? 'white'" | ||
| :icon="entryType.icon ?? 'empty'" | ||
| :checked=" | ||
| modelValue.find((valueType) => valueType.id === entryType.id) | ||
| " | ||
| :data-color="entryType.color?.value ?? 'white'" | ||
| > |
There was a problem hiding this comment.
<craft-action-item>’s icon property is defined as string | null (it renders <craft-icon name="${icon}">). Here entryType.icon is an object (name/family/variant), so binding it to :icon will result in a non-string value and likely render incorrectly. Pass a string icon name (e.g. entryType.icon?.name) or provide a <craft-icon slot="icon" ...> instead.
| description: string | null; | ||
| icon?: null | { | ||
| name: string; | ||
| family: string; | ||
| variant: string; | ||
| }; |
There was a problem hiding this comment.
EntryType.icon is typed as an object with required name, family, and variant, but the backend resource currently returns only name and family (no variant). This mismatch will either break TypeScript or force runtime undefined values when spreading into <craft-icon v-bind="icon" />. Align the API payload and the TS type (either make variant optional or include it in the resource output).
| watch(errorFlash, (newMessage) => announce(newMessage)); | ||
|
|
||
| useAppendHtml(); | ||
|
|
There was a problem hiding this comment.
useAppendHtml() is called without a source object and its return value is unused, so it currently has no effect (the internal watch() only runs when source is provided). Either pass the Inertia props that contain headHtml/bodyHtml, or remove this call to avoid dead code.
| /** | ||
| * Composable for appending HTML to the document head or body. | ||
| * | ||
| * Handles deduplication of CSS and JS resources automatically, | ||
| * ensuring that stylesheets and scripts are only loaded once. | ||
| * headHtml is always loaded and executed before bodyHtml. | ||
| * |
There was a problem hiding this comment.
The docstring says “headHtml is always loaded and executed before bodyHtml”, but appendHeadHtml() / appendBodyHtml() only append nodes; they don’t await external <script src> load/execution. If consumers rely on execution order (especially for legacy globals), this comment is misleading. Either adjust the docs to “appended before” or update the DOM utility to await script load events when needed.
| "no-descending-specificity": null, | ||
| "no-duplicate-selectors": null, | ||
| "no-invalid-position-at-import-rule": [ | ||
| true, | ||
| { | ||
| "ignoreAtRules": ["tailwind", "use"] | ||
| } | ||
| ], | ||
| "no-invalid-position-at-import-rule": null, | ||
| "selector-class-pattern": null, |
There was a problem hiding this comment.
Setting no-invalid-position-at-import-rule to null disables the check project-wide, which can allow real @import ordering issues to slip in unnoticed. If this was done to accommodate specific at-rules (e.g. Tailwind/Sass), consider keeping the rule enabled and adjusting ignoreAtRules instead so you still get protection for plain CSS @import ordering mistakes.
This brings some compatibility between the legacy javascript and our new inertia and Vue world. The result of all of this is a functional Section settings page which is initially rendered via Inertia but calls legacy code in order to render the slideouts and nested slideouts.
CleanShot.2026-04-08.at.14.33.30.mp4
CpAssetBundle
The
CpAssetBundleis now loaded in both the Inertia and Twig render paths. Twig works as it always has. On the Inertia side, theHandleInertiaRequestsmiddleware will register theCpAssetBundleand then inject theheadHtmlandbodyHtmlinto theapp.blade.phptemplate. That will throw the same oldCraftglobal variable you know and love onto the page. With the variable on the page, when thecp.jsfile gets loaded by its position inbodyHtmlit will populate the global variable with all the methods we've always had.This means you can call your
Craft.*Javascript just like you always have. The one caveat is that it's more possible than previously to create click handlers, etc. for content that will be loaded in after the fact.At the moment you have both the legacy
Craftglobal variable and the newCpvariable. Over time, we'll move methods and configuration fromCrafttoCp.Styling
Styling presented a bit of a challenge as we can have both old and new markup on a page at one time (if a slideout with legacy content is rendered for example). This can make styles a bit messy in some places, but I've done my best to isolate things.
The
TailwindResetAssetwill now only apply styles to.cp-legacy-reset, .slideout-container, .menu--disclosureand (almost) all legacy styles will only kick in when wrapped with a.cp-legacyclass.This falls down a bit for modals, menus, huds, etc. because we render those at the document root instead of in a container where we can add the
cp-legacyclass. Those styles are still applied globally for now.Potential Breaking Changes
{% js %}twig tags will now result in a<script type="module">tag. In theory, this shouldn't break anything because the load order will still be respected, but it is a shift.